package klotski;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;
import java.util.Vector;

import model.Board;
import model.Board.Move;

public class KlotskiBoard extends Board {

    public Vector<Block> blocks;
    public Block WALL = new Block();
    public Stack<KMove> history;
    protected Block EXIT = new Block();
    int work = 0;
    static Direction dirs[] = new Direction[4];

    {
        dirs[0] = new Direction(1, 0);
        dirs[1] = new Direction(0, 1);
        dirs[2] = new Direction(-1, 0);
        dirs[3] = new Direction(0, -1);

    }
    int winx;
    int winy;
    public int w;
    public int h;
    public Block board[][];
    public Block donkey;
    long hashValues[][][];

    public void reset() {

     //  blocks.clear();
        board = new Block[w][h];
        history = new Stack<KMove>();

        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                board[i][j] = null;
            }
        }

        history.clear();
        work = 0;
        for (int i = 0; i < w; i++) {
            board[i][0] = WALL;
            board[i][h - 1] = WALL;
        }

        for (int j = 0; j < h; j++) {
            board[0][j] = WALL;
            board[w - 1][j] = WALL;
        }

        blocks = new Vector<Block>();

        hashValues = new long[ProtoBlock.typeCount][w][h];
        Random r = new Random();

        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                for (int t = 0; t < ProtoBlock.typeCount; t++) {
                    // System.out.println(i + " " + j + " " + board[i][j]);
                    hashValues[t][i][j] = r.nextLong();
                }
            }
        }

    }

    protected KlotskiBoard(int w, int h, int winx, int winy) {
        this.winx = winx;
        this.winy = winy;
        this.w = w;
        this.h = h;
        reset();
      

    }

    protected void addBlock(int x, int y, ProtoBlock type) {
        Block bloc = new Block(x, y, type);
        blocks.add(bloc);

        for (int i = 0; i < type.pieces.length; i++) {
            int pos[] = type.pieces[i];
            int xdst = x + pos[0];
            int ydst = y + pos[1];
            Block dst = board[xdst][ydst];
            assert (dst == null);
            board[xdst][ydst] = bloc;
        }

    }

    public boolean undo() {
        if (history.isEmpty()) {
            return false;
        }
        KMove move = history.pop();
        move.unMove();
        return true;
    }

    public class Block {

        public int x;
        public int y;
        public ProtoBlock type;

        Block() {
        }

        Block(int x, int y, ProtoBlock type) {
            this.x = x;
            this.y = y;
            this.type = type;

        }

        public boolean canMove(int d) {
            return canMove(dirs[d]);
        }

        private boolean canMove(Direction dir) {

            for (int i = 0; i < type.pieces.length; i++) {
                int xdst = x + type.pieces[i][0] + dir.dx;
                int ydst = y + type.pieces[i][1] + dir.dy;

                Block dst = board[xdst][ydst];

                if (dst == null || dst == this) {
                    continue;
                }
                return false;
            }

            return true;

        }

        private void doMove(int d) {
            doMove(dirs[d]);
        }

        private void doMove(Direction dir) {
            for (int i = 0; i < type.pieces.length; i++) {
                board[x + type.pieces[i][0]][y + type.pieces[i][1]] = null;
            }

            x += dir.dx;
            y += dir.dy;
            for (int i = 0; i < type.pieces.length; i++) {
                board[x + type.pieces[i][0]][y + type.pieces[i][1]] = this;
            }
            work++;
            setChanged();
        }

        public String toString() {
            if (type == null) {
                return "EMPTY";
            }
            String str = type + "(" + x + "," + y + ")";
            return str;
        }

        public String getIndex() {
            if (this == WALL) {
                return "X";
            }
            if (type == null) {
                return " ";
            }
            return "" + type.id;
        }

        private void unMove(int dir) {
            doMove((dir + 2) % 4);
        }
    }

    public boolean hasWon() {
        return donkey.x == winx && donkey.y == winy;
    }

    public long longHashCode() {
        long hash = 0;
        for (Block b : blocks) {
            // Board.Type type = b.typeproto.type;
            int t = b.type.id;
            hash = hash ^ hashValues[t][b.x][b.y];
        }
        return hash;

    }

    public void asciiDump() {

        for (int j = 0; j < h; j++) {
            System.out.println();
            for (int i = 0; i < w; i++) {
                Block b = board[i][j];
                if (b == null) {
                    System.out.print(" ");
                } else {
                    System.out.print(b.getIndex());
                }
            }
        }
        System.out.println();

    }

    public class KMove extends Move {

        KlotskiBoard.Block block;
        int dir;

        public KMove(KlotskiBoard.Block block, int dir) {
            this.block = block;
            this.dir = dir;
        }

        public void doMove() {
            block.doMove(dir);
            history.push(this);
        }

        public void unMove() {
            block.doMove((dir + 2) % 4);
            //history.push(this);
        }
    }
    ArrayList<Move> moves = new ArrayList<Move>();

    public List<Move> getMoves() {
        moves.clear();
        for (KlotskiBoard.Block b : blocks) {
            for (int i = 0; i < 4; i++) {
                if (b.canMove(i)) {
                    moves.add(new KMove(b, i));
                    //		System.out.println("MOVE :" + b  + " " + board.dirs[i]);
                }
            }
        }

        return moves;
    }

    public int getDepth() {
        return history.size();
    }

    public int getWork() {
        return work;
    }

    public class Direction {

        int dx;
        int dy;

        Direction(int dx, int dy) {
            this.dx = dx;
            this.dy = dy;
        }

        public String toString() {
            return "<" + dx + "," + dy + ">";
        }
    }
}
